package com.zkt.recommend.domain.algorithm;

import com.zkt.recommend.common.constants.RecommendationProperties;
import com.zkt.recommend.infra.entity.SubjectUserRecord;
import com.zkt.recommend.domain.utils.DateUtil;
import com.zkt.recommend.infra.basic.entity.RecommendUserPortrait;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.List;

/**
 * @author 赵开泰
 * @program jc-club
 * @date 2025/3/29
 * @description 用户画像计算
 **/

@Component
public class UserPortraitCalculator {
	
	@Resource
	private RecommendationProperties properties;
	
	/**
	 * 基于已有用户画像 + 新做题记录 更新用户画像
	 */
	public RecommendUserPortrait updateUserPortrait(SubjectUserRecord record, RecommendUserPortrait existingPortrait) {
		// 获取权重配置
		double w1 = properties.getMasteryScore();
		double w2 = properties.getLastPracticeTime();
		double w3 = properties.getPracticeCount();
		double w4 = properties.getAverageTime();
		double w5 = properties.getAverageScoreRate();
		
		// 计算正确率
		BigDecimal correctRate = (record.getSubjectScore().compareTo(0) > 0)
				? BigDecimal.valueOf(record.getUserScore()).divide(BigDecimal.valueOf(record.getSubjectScore()), 4, RoundingMode.HALF_UP)
				: BigDecimal.ZERO;
		
		// 计算遗忘曲线影响权重
		double forgettingWeight = DateUtil.calculateForgettingWeight(record.getCreatedTime());
		
		RecommendUserPortrait recommendUserPortrait = new RecommendUserPortrait();
		if (existingPortrait == null) {
			// 该标签第一次做题，初始化用户画像
			
			recommendUserPortrait.setUserId(record.getUserId());
			recommendUserPortrait.setLabelId(record.getLabelId());
			recommendUserPortrait.setMasteryScore(100);
			recommendUserPortrait.setLastPracticeTime(record.getCreatedTime());
			recommendUserPortrait.setPracticeCount(1);
			recommendUserPortrait.setAvgDuration(BigDecimal.valueOf(record.getUseTime()));
			recommendUserPortrait.setAvgScore(correctRate.multiply(BigDecimal.valueOf(100)));
			
			return recommendUserPortrait;
		}
		
		// 计算新的练习次数
		int newPracticeCount = existingPortrait.getPracticeCount() + 1;
		
		// 计算新的平均耗时
		BigDecimal newAvgTime = (existingPortrait.getAvgDuration()
				.multiply(BigDecimal.valueOf(existingPortrait.getPracticeCount()))
				.add(BigDecimal.valueOf(record.getUseTime())))
				.divide(BigDecimal.valueOf(newPracticeCount), 4, RoundingMode.HALF_UP);
		
		// 计算新的平均得分率
		BigDecimal newAvgScoreRate = (existingPortrait.getAvgScore()
				.multiply(BigDecimal.valueOf(existingPortrait.getPracticeCount()))
				.add(correctRate.multiply(BigDecimal.valueOf(100))))
				.divide(BigDecimal.valueOf(newPracticeCount), 4, RoundingMode.HALF_UP);
		
		// 计算新的掌握度（综合考虑正确率、时间衰减、练习次数）
		BigDecimal newMasteryScore = newAvgScoreRate
				.multiply(BigDecimal.valueOf(w1))
				.add(BigDecimal.valueOf(forgettingWeight * w2))
				.add(BigDecimal.valueOf(newPracticeCount * w3))
				.add(BigDecimal.valueOf((100 - newAvgTime.doubleValue()) * w4))
				.add(BigDecimal.valueOf((100 - newAvgScoreRate.doubleValue()) * w5))
				.setScale(0, RoundingMode.HALF_UP);
		
		recommendUserPortrait.setId(existingPortrait.getId());
		recommendUserPortrait.setUserId(existingPortrait.getUserId());
		recommendUserPortrait.setLabelId(existingPortrait.getLabelId());
		recommendUserPortrait.setMasteryScore(newMasteryScore.intValue());
		recommendUserPortrait.setLastPracticeTime(record.getCreatedTime());
		recommendUserPortrait.setPracticeCount(newPracticeCount);
		recommendUserPortrait.setAvgDuration(newAvgTime);
		recommendUserPortrait.setAvgScore(newAvgScoreRate);
		
		return recommendUserPortrait;
	}
	
	/**
	 * 基于用户的所有做题记录 计算用户画像
	 */
	public RecommendUserPortrait calculateUserPortrait(Long userId, Long labelId, List<SubjectUserRecord> records) {
		if (records.isEmpty()) {
			return null;
		}
		
		// 获取权重配置
		double w1 = properties.getMasteryScore();
		double w2 = properties.getLastPracticeTime();
		double w3 = properties.getPracticeCount();
		double w4 = properties.getAverageTime();
		double w5 = properties.getAverageScoreRate();
		
		int totalPractice = records.size();
		BigDecimal totalTime = BigDecimal.ZERO, totalScoreRate = BigDecimal.ZERO;
		LocalDateTime lastPractice = records.get(0).getCreatedTime();
		
		for (SubjectUserRecord record : records) {
			BigDecimal correctRate = (record.getSubjectScore().compareTo(0) > 0)
					? BigDecimal.valueOf(record.getUserScore()).divide(BigDecimal.valueOf(record.getSubjectScore()), 4,
					RoundingMode.HALF_UP)
					: BigDecimal.ZERO;
			
			totalTime = totalTime.add(BigDecimal.valueOf(record.getUseTime()));
			totalScoreRate = totalScoreRate.add(correctRate.multiply(BigDecimal.valueOf(100)));
			
			if (record.getCreatedTime().isAfter(lastPractice)) {
				lastPractice = record.getCreatedTime();
			}
		}
		
		BigDecimal avgTime = totalTime.divide(BigDecimal.valueOf(totalPractice), 4, RoundingMode.HALF_UP);
		BigDecimal avgScoreRate = totalScoreRate.divide(BigDecimal.valueOf(totalPractice), 4, RoundingMode.HALF_UP);
		
		// 计算遗忘曲线影响权重
		double forgettingWeight = DateUtil.calculateForgettingWeight(lastPractice);
		
		// 计算掌握度
		BigDecimal masteryScore = avgScoreRate.multiply(BigDecimal.valueOf(w1))
				.add(BigDecimal.valueOf(forgettingWeight * w2))
				.add(BigDecimal.valueOf(totalPractice * w3))
				.add(BigDecimal.valueOf((100 - avgTime.doubleValue()) * w4))
				.add(BigDecimal.valueOf((100 - avgScoreRate.doubleValue()) * w5))
				.setScale(0, RoundingMode.HALF_UP);
		
		RecommendUserPortrait recommendUserPortrait = new RecommendUserPortrait();
		
		recommendUserPortrait.setUserId(userId);
		recommendUserPortrait.setLabelId(labelId);
		recommendUserPortrait.setMasteryScore(masteryScore.intValue());
		recommendUserPortrait.setLastPracticeTime(lastPractice);
		recommendUserPortrait.setPracticeCount(totalPractice);
		recommendUserPortrait.setAvgDuration(avgTime);
		recommendUserPortrait.setAvgScore(avgScoreRate);
		
		return recommendUserPortrait;
	}
	
}